package main

import (
	"fmt"
	"io/ioutil"
	"ndemo/mynl"
	"os"
)

/**
 * 第4节
 * 知识点：
 * 1.语法分析：变量声明和变量赋值
 * 2.符号表
 * 3.变量的引用消解
 * 4.解释器：变量的存取
 * 5.左值
 *
 * 本节采用的词法规则是比较精简的，比如不考虑Unicode。
 * Identifier: [a-zA-Z_][a-zA-Z0-9_]*
 *
 * EBNF规则：
 * prog = (functionDecl | functionCall)*
 * functionDecl: "function" Identifier "(" ")"  functionBody
 * functionBody : '{' functionCall* '}'
 * functionCall : Identifier '(' parameterList? ')'
 * parameterList : StringLiteral (',' StringLiteral)*
 *
 * 其他：
 * 系统调用以nl.开始
 * strings.HasPrefix(functionCall.name, "nl.")
 *
 * 运行：
 * export GO111MODULE=auto
 * go env -w GOPROXY=https://goproxy.cn,direct
 * go build
 *
 */

/////////////////////////////////////////////////////////////////////////
// 主程序

func compileAndRun(program string) {
	//源代码
	//fmt.Println("源代码:")
	//fmt.Println(program)

	//词法分析
	fmt.Println("\n词法分析结果:")
	var tokenizer = mynl.NewScanner(*mynl.NewCharStream(program))
	for {
		if tokenizer.Peek().Kind == mynl.TokenKind_EOF {
			break
		}
		fmt.Println(tokenizer.Next())
	}

	//重置tokenizer,回到开头
	tokenizer = mynl.NewScanner(*mynl.NewCharStream(program))

	//语法分析
	var pprog = mynl.NewParser(*tokenizer).ParseProg()
	var prognode mynl.IAstNode = pprog
	fmt.Println("\n语法分析后的AST:")
	prognode.Dump("")

	//语义分析
	//建立符号表
	var symTable = mynl.NewSymTable()
	var enter = mynl.NewEnter(*symTable)
	var enterNode mynl.IAstVisitor = *enter
	(*enter).Visit(&enterNode, &prognode)

	//引用消解
	var refresolver = mynl.NewRefResolver(*symTable)
	var refNode mynl.IAstVisitor = *refresolver
	(*refresolver).Visit(&refNode, &prognode)
	fmt.Println("\n语义分析后的AST，注意变量和函数已被消解:")
	prognode.Dump("")

	//运行程序
	var intepretor = mynl.NewIntepretor()
	var intNode mynl.IAstVisitor = *intepretor
	fmt.Println("\n运行当前的程序:")
	var retVal = (*intepretor).Visit(&intNode, &prognode)
	fmt.Println("程序返回值：", retVal)
}

/////////////////////////////////////////////////////////////////////////
//程序入口
//处理命令行参数，从文件里读取源代码
func main() {
	//运行示例
	// 要求命令行的第三个参数，一定是一个文件名。
	if len(os.Args) < 2 {
		fmt.Println("Usage: " + os.Args[0] + " FILENAME")
		return
	}

	// 读取源代码
	var filename = os.Args[1]
	f, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println("read fail", err)
		return
	}

	compileAndRun(string(f))
}
